// file tools
// created: 2018/7/30
// author: wdj

package file

import (
	"bufio"
	"fmt"
	myErr "gitee.com/flwwsg/utils-go/error"
	"golang.org/x/crypto/ssh/terminal"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"reflect"
	"regexp"
	"runtime"
	"strings"
)

// ListDir list only  directories or only file in given file path
func ListDir(fpath string, fullPath bool, onlyDir bool) []string {
	files, err := ioutil.ReadDir(fpath)
	dirs := make([]string, 0)
	fileName := ""
	if err != nil {
		log.Printf("list error path %s", fpath)
		log.Fatal(err)
	}
	for _, f := range files {
		if fullPath {
			fileName = path.Join(fpath, f.Name())
		} else {
			fileName = f.Name()
		}
		if f.IsDir() == onlyDir {
			// list only dir or only file
			dirs = append(dirs, fileName)
		}
	}
	return dirs
}

// FindProjectRoot get project path from GOPATH
func FindProjectRoot(name string) string {
	gopath := os.Getenv("GOPATH")
	var mulPath []string
	// check in windows
	if runtime.GOOS == "windows" {
		mulPath = strings.Split(gopath, ";")
	} else {
		// check in linux or mac
		mulPath = strings.Split(gopath, ":")
	}

	for _, p := range mulPath {
		src := filepath.Join(p, "src")
		dirs := ListDir(src, false, true)
		if FindInList(name, dirs) {
			return filepath.Join(src, name)
		}
	}
	panic(fmt.Sprintf("project named %s not found", name))
}

// 包路径
func FullPackagePath(pkgPath string) string {
	gopath := os.Getenv("GOPATH")
	var mulPath []string
	// check in windows
	if runtime.GOOS == "windows" {
		mulPath = strings.Split(gopath, ";")
	} else {
		// check in linux or mac
		mulPath = strings.Split(gopath, ":")
	}

	for _, p := range mulPath {
		src := filepath.Join(p, "src")
		fullPath := filepath.Join(src, pkgPath)
		_, err := os.Stat(fullPath)
		if err == nil || !os.IsNotExist(err) {
			return fullPath
		}
	}
	panic(fmt.Errorf("no such pacakge with path %s ", pkgPath))
}

// FindInList find item in given list
func FindInList(item interface{}, list interface{}) bool {
	switch t := reflect.TypeOf(list).Kind(); t {
	case reflect.Slice:
		val := reflect.ValueOf(list)
		for i := 0; i < val.Len(); i++ {
			if reflect.DeepEqual(item, val.Index(i).Interface()) {
				return true
			}
		}
	default:
		panic(fmt.Sprintf("%s not type of slice ", t))
	}

	return false
}

// SaveFile 保存文件
func SaveFile(fileName string, textString interface{}, isForce bool) error {
	_, err := os.Stat(fileName)
	if os.IsExist(err) && !isForce {
		// 文件存在且不覆盖
		return os.ErrExist
	}
	var text []byte
	ts, ok := textString.(string)
	if ok {
		text = []byte(ts)
	}
	tb, ok := textString.([]byte)
	if ok {
		text = tb
	}
	if len(text) == 0 {
		panic(fmt.Sprintf("invalid type of %v", textString))
	}
	return ioutil.WriteFile(fileName, text, os.ModePerm)
}

// TODO 检查正确性
func GoInstall(pkg string, tags string) {
	var args []string
	var err error
	if tags != "" {
		args = append(args, "-tags")
		args = append(args, tags)
	}
	// 兼容性考虑
	err = os.Setenv("GO111MODULE", "auto")
	myErr.PanicOnErr(err)
	pkgPath := FullPackagePath(pkg)
	args = append(args, pkgPath+"/...")
	log.Print("cmd ", strings.Join(args, " "))
	cmd := exec.Command("go", "install", strings.Join(args, " "))
	err = cmd.Run()
	myErr.PanicOnErr(err)
}

// 检查文件是否存在
func IsFileExist(pathToCheck string) bool {
	_, err := os.Stat(pathToCheck)
	if err == nil || !os.IsNotExist(err) {
		return true
	}
	return false
}

// ReadPassword 从stdin读取密码
func ReadPassword(prompt string) (passwd string, err error) {
	state, err := terminal.MakeRaw(int(os.Stdin.Fd()))
	if err != nil {
		return "", err
	}
	defer func() {
		err := terminal.Restore(int(os.Stdin.Fd()), state)
		// 处理错误
		if err != nil {
			panic(err)
		}
	}()
	return terminal.NewTerminal(os.Stdin, "").ReadPassword(prompt)
}

//  RexTest 检查是否满足正则，不允许 panic
func RexTest(elem string, testRex ...string) bool {
	if len(testRex) < 1 {
		return false
	}
	regexString := "(" + strings.Join(testRex, ")|(") + ")"
	valid := regexp.MustCompile(regexString)
	return valid.MatchString(elem)
}

// ReadLines 从 stdin 读取输入文本, 回车键结束
func ReadLines(prompt string) (lines []string, err error) {
	println(prompt)
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {

		if err := scanner.Err(); err != nil {
			return nil, err
		}
		text := scanner.Text()
		if text == "" {
			break
		}
		lines = append(lines, text)
	}
	return lines, nil
}

func ReadLine(prompt string) (line string, err error) {
	println(prompt)
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	return scanner.Text(), scanner.Err()
}
